问:
Call a method for List in parallel?
I must iterate over an list and call for every object a method, but in parallel. After the loop, there other statements, which must wait for the parallel method invocations. How can I do that in JAVA? </p>

```<code>public void a(List<Object> list) {
    for(Object o : list) {
        asynchMethod(o); // this n method call must run in the same time
    }

    // wait for all asynchMethod result
    /**
     * ...other statements
     */
}

private void asynchMethod(Object o) {
    // some code
}
</code>```

答:
One fool proof way (out of many) to execute any method in parallel is to start a thread pool and then assign tasks to it and wait for the tasks to finish.

```<code>public static ThreadPoolExecutor getExecutorService(int poolSize, int maxPoolSize{
  int threadPoolshutDownTime = 10L;
  ThreadPoolExecutor executorService= new ThreadPoolExecutor(poolSize, maxPoolSize, threadPoolshutDownTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
  executorService.allowCoreThreadTimeOut(true); // allows the threadpool to shutdown if no task is assigned
  return executorService;
}

</code>```

Now call this inside your method like the following :

```<code>public void a(List<Object> list) throws InterruptedException, ExecutionException  {

  List<Callable<Boolean>> callables = new ArrayList<>(list.size());
  list.forEach(object ->callables.add(() -> return asynchMethod(object)));

  for (Future<Boolean> booleanFuture : this.getExecutorService(1,4).invokeAll(callables)) {
            booleanFuture.get(); //this will wait for the callables to be done!
  }
}
</code>```

Also modify your aysncMethod as the following :

```<code>private boolean asynchMethod(Object o) {
    return o.doMagic(); //doMagic returns a boolean when completed
}
</code>```

Title: Call a method for List in parallel?

Body:
I must iterate over an list and call for every object a method, but in parallel. After the loop, there other statements, which must wait for the parallel method invocations. How can I do that in JAVA?

public void a(List<Object> list) {
    for(Object o : list) {
        asynchMethod(o); // this n method call must run in the same time
    }

    // wait for all asynchMethod result
    /**
     * ...other statements
     */
}

private void asynchMethod(Object o) {
    // some code
}

Body:
One fool proof way (out of many) to execute any method in parallel is to start a thread pool and then assign tasks to it and wait for the tasks to finish.

public static ThreadPoolExecutor getExecutorService(int poolSize, int maxPoolSize{
  int threadPoolshutDownTime = 10L;
  ThreadPoolExecutor executorService= new ThreadPoolExecutor(poolSize, maxPoolSize, threadPoolshutDownTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
  executorService.allowCoreThreadTimeOut(true); // allows the threadpool to shutdown if no task is assigned
  return executorService;
}

Now call this inside your method like the following :

public void a(List<Object> list) throws InterruptedException, ExecutionException  {

  List<Callable<Boolean>> callables = new ArrayList<>(list.size());
  list.forEach(object ->callables.add(() -> return asynchMethod(object)));

  for (Future<Boolean> booleanFuture : this.getExecutorService(1,4).invokeAll(callables)) {
            booleanFuture.get(); //this will wait for the callables to be done!
  }
}

Also modify your aysncMethod as the following :

private boolean asynchMethod(Object o) {
    return o.doMagic(); //doMagic returns a boolean when completed
}


来源: stackexchange

问:
Use value of an Input in a function
I'm a bit ashamed to write this topic because even if I got a HTML formation (4 years ago) I can't resolve my problem :

I can't use the value of an input type text in a function, this is my code:

import React from 'react'
//import db from '../../constants/FirebaseConfig'

class Account extends React.Component {

    state = {
        name: '',
        content: ''
    }

    _formSubmit(e) {
        e.preventDefault()
        var name = document.getElementsByName('name')
        console.log(name)

    }

    render(){
        return(

Account

                <form id="form">

                    Titre
                    <input type="text" name="name" placeholder="Ici le nom de l'Article"/>

                    Contenue
                    <input type="text" name="content" placeholder="Ici le contenue de l'article"/>

                    <input type="button" value="suivant" onClick={(e) =>this._formSubmit(e)}/>

                </form>

        )
    }
}

export default Account```

After attempting to debug a few times I think the problem is with the var name object which is a  NodeList<input> type.
My goal is to stock the 'name' and 'content' value in a firebase db.

Thank You

答:
Without suggesting any workaround but trying to make exactly what you posted work the way you're expecting it to, Here's what I figured out you were trying to do:

Your Problem:

```<code>_formSubmit(e) {
    e.preventDefault()
    var name = document.getElementsByName('name') //Get's a list of Elements with the given name
    console.log(name) // This prints the list contained in name variable

}
</code>```

The Solution:

```<code>_formSubmit(e) {
    e.preventDefault()

   // Assuming you're trying to print the values of all inputs
   // First - Get all elements whose values you want to log, as per your initial code
    var name = document.getElementsByName('name'); // returns ListNode<HTMLElement>

   // Now loop through each input and print it's value to console
   name.forEach(
     function(input){
      console.log(input.value); //Print the value of each input listed
     }
   );
}
</code>```

ContentLicense: CC BY-SA 4.0

Title: Use value of an Input in a function

Body:
I'm a bit ashamed to write this topic because even if I got a HTML formation (4 years ago) I can't resolve my problem :

I can't use the value of an input type text in a function, this is my code:

import React from 'react'
//import db from '../../constants/FirebaseConfig'

class Account extends React.Component {

    state = {
        name: '',
        content: ''
    }

    _formSubmit(e) {
        e.preventDefault()
        var name = document.getElementsByName('name')
        console.log(name)

    }

    render(){
        return(

Account

                <form id="form">

                    Titre
                    <input type="text" name="name" placeholder="Ici le nom de l'Article"/>

                    Contenue
                    <input type="text" name="content" placeholder="Ici le contenue de l'article"/>

                    <input type="button" value="suivant" onClick={(e) =>this._formSubmit(e)}/>

                </form>

        )
    }
}

export default Account

After attempting to debug a few times I think the problem is with the var name object which is a  NodeList<input> type.
My goal is to stock the 'name' and 'content' value in a firebase db.

Thank You

Body:
Without suggesting any workaround but trying to make exactly what you posted work the way you're expecting it to, Here's what I figured out you were trying to do:

Your Problem:

_formSubmit(e) {
    e.preventDefault()
    var name = document.getElementsByName('name') //Get's a list of Elements with the given name
    console.log(name) // This prints the list contained in name variable

}

The Solution:

_formSubmit(e) {
    e.preventDefault()

   // Assuming you're trying to print the values of all inputs
   // First - Get all elements whose values you want to log, as per your initial code
    var name = document.getElementsByName('name'); // returns ListNode<HTMLElement>

   // Now loop through each input and print it's value to console
   name.forEach(
     function(input){
      console.log(input.value); //Print the value of each input listed
     }
   );
}


问:
How to add custom navbar back button in SwiftUI
I have been trying to look up how to add custom navbar back button in SwiftUI but I get this strange behaviour, that the default behaviour still appears before the custom one is shown. does anyone know the proper way to add it?

Here is what I tried.

```<code>var body: some View {
        NavigationView {
            ZStack {
               Color.background.edgesIgnoringSafeArea(.all)
               NavigationLink(destination: UserDetailsView()) {
                        Text("Continue")
                            .foregroundColor(.background)
                            .font(.title)
                            .fontWeight(.semibold)
                    }
                    .frame(width: 250, height: 60, alignment: .center)
                    .background(Color.white)
                    .cornerRadius(40)
                    .padding(.top, 50)
            }
            .navigationBarTitle("", displayMode: .automatic)
            .navigationBarHidden(true)
        }
      }
</code>```

Here when I use the below code to hide back button it doesn't even hide at all.

```<code>.navigationBarBackButtonHidden(true)
</code>```

In details screen

```<code>  var body: some View {
        NavigationView {
            VStack {
                ZStack {
                    Rectangle()
                        .foregroundColor(.clear)
                        .background(gradient)
                        .edgesIgnoringSafeArea(.all)

                    Text("Hello")
                }
            }
            .navigationBarItems(leading: BackButton(presentationMode: presentationMode))
        }
    }
</code>```

The custom back button looks like this

```<code>struct BackButton: View {
    var presentationMode : Binding<PresentationMode>

    var body: some View {
        Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
                Image(Icon.leftArrow)
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.black)
            }
        }
    }
}
</code>```

Here is the what it looks like

答:
Thanks for the hints. It works for me.... BUT after adding a

```<code>.navigationBarItems(leading:  ...
</code>```

the swipe-to-go-back no longer works.
I'm guessing this is a bug in SwiftUI ?

Have anybody found a workaround for this?

Body:
I have been trying to look up how to add custom navbar back button in SwiftUI but I get this strange behaviour, that the default behaviour still appears before the custom one is shown. does anyone know the proper way to add it?

Here is what I tried.

var body: some View {
        NavigationView {
            ZStack {
               Color.background.edgesIgnoringSafeArea(.all)
               NavigationLink(destination: UserDetailsView()) {
                        Text("Continue")
                            .foregroundColor(.background)
                            .font(.title)
                            .fontWeight(.semibold)
                    }
                    .frame(width: 250, height: 60, alignment: .center)
                    .background(Color.white)
                    .cornerRadius(40)
                    .padding(.top, 50)
            }
            .navigationBarTitle("", displayMode: .automatic)
            .navigationBarHidden(true)
        }
      }

Here when I use the below code to hide back button it doesn't even hide at all.

.navigationBarBackButtonHidden(true)

In details screen

  var body: some View {
        NavigationView {
            VStack {
                ZStack {
                    Rectangle()
                        .foregroundColor(.clear)
                        .background(gradient)
                        .edgesIgnoringSafeArea(.all)

                    Text("Hello")
                }
            }
            .navigationBarItems(leading: BackButton(presentationMode: presentationMode))
        }
    }

The custom back button looks like this

struct BackButton: View {
    var presentationMode : Binding<PresentationMode>

    var body: some View {
        Button(action: {
            self.presentationMode.wrappedValue.dismiss()
        }) {
            HStack {
                Image(Icon.leftArrow)
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.black)
            }
        }
    }
}

Here is the what it looks like

Body:
Thanks for the hints. It works for me.... BUT after adding a

.navigationBarItems(leading:  ...

the swipe-to-go-back no longer works.
I'm guessing this is a bug in SwiftUI ?

Have anybody found a workaround for this?


问:
get of Map<string,string> not a function in Angualr Testing
Angular testing throws
TypeError: _co.mapNumbers.get is not a function
in the test running with Karma. Component works fine and code is like below:

In component I have declared:

```<code>export class A{
     mapNumbers:Map<string,string>;
     ngOnInit() {
       mapNumbers = JSONOBJECT[0] // Assignment of values works fine and also matches the type
}
</code>```

In template:

```<code><span>
          {{mapNumbers.get('24_HOURS_ASSISTANCE_PHONE_NUMBER')
            ) | translate
          }}
        </span>
</code>```

It's nothing complex but seems it does not work for Karma, Do I need to do a special import for Map here?

答:
It's strange but seems like unless I use Map's set method direct assigning only works with the app but not unit testing:</p>

```<code>arrayOutput = new Map();

arrayInput.foreach((value: string, key: string) => {
        this.arryOutput.set(key,value);  // Unit test fails unless assing the values through set method
    });
</code>```

Body:
Angular testing throws
TypeError: _co.mapNumbers.get is not a function
in the test running with Karma. Component works fine and code is like below:

In component I have declared:

export class A{
     mapNumbers:Map<string,string>;
     ngOnInit() {
       mapNumbers = JSONOBJECT[0] // Assignment of values works fine and also matches the type
}

In template:

          {{mapNumbers.get('24_HOURS_ASSISTANCE_PHONE_NUMBER')
            ) | translate
          }}

It's nothing complex but seems it does not work for Karma, Do I need to do a special import for Map here?

Body:
It's strange but seems like unless I use Map's set method direct assigning only works with the app but not unit testing:

arrayOutput = new Map();

arrayInput.foreach((value: string, key: string) => {
        this.arryOutput.set(key,value);  // Unit test fails unless assing the values through set method
    });


问:
How to use R-exams package to create a multi-part question?
Suppose I had an exam question such as "A car starting from rest accelerates to 26.82 m/s in 3.2 s. Compute the acceleration during this time. How far did the car travel in this time?"

I'm new to R-exams package. In the past I've used MIT's "exam" LaTex package. There, I'd do something like:

```<code>\begin{question}
A car starting from rest accelerates to 26.82 m/s in 3.2 s.
\begin{parts}
\part Compute the acceleration during this time.
\part How far did the car travel in this time?
\end{parts}
</code>```

I've poked around the r-exams.org but don't see any examples of creating such a multipart question.

答:
For combining multiple questions in a single exercise you can use the "cloze" type: http://www.R-exams.org/intro/dynamic/. Some concrete examples are linked from there.

In your case, if you want to use R/LaTeX markup (.Rnw) in the exercise, the question would become:

```<code>\begin{question}
A car starting from rest accelerates to 26.82 m/s in 3.2 s.
\begin{answerlist}
\item Compute the acceleration during this time.
\item How far did the car travel in this time?
\end{answerlist}
\end{question}
</code>```

The corresponding meta-information needs to include something like:

```<code>\extype{cloze}
\exclozetype{num|num}
\exsolution{8.38125|42.912}
</code>```

R/Markdown (.Rmd) would, of course, be possible as well. And both are hopefully straightforward to turn into dynamic exercises where the numbers are sampled randomly. Let us know if you need further help with this.

Body:
Suppose I had an exam question such as "A car starting from rest accelerates to 26.82 m/s in 3.2 s. Compute the acceleration during this time. How far did the car travel in this time?"

I'm new to R-exams package. In the past I've used MIT's "exam" LaTex package. There, I'd do something like:

\begin{question}
A car starting from rest accelerates to 26.82 m/s in 3.2 s.
\begin{parts}
\part Compute the acceleration during this time.
\part How far did the car travel in this time?
\end{parts}

I've poked around the r-exams.org but don't see any examples of creating such a multipart question.

Body:
For combining multiple questions in a single exercise you can use the "cloze" type: http://www.R-exams.org/intro/dynamic/. Some concrete examples are linked from there.

In your case, if you want to use R/LaTeX markup (.Rnw) in the exercise, the question would become:

\begin{question}
A car starting from rest accelerates to 26.82 m/s in 3.2 s.
\begin{answerlist}
\item Compute the acceleration during this time.
\item How far did the car travel in this time?
\end{answerlist}
\end{question}

The corresponding meta-information needs to include something like:

\extype{cloze}
\exclozetype{num|num}
\exsolution{8.38125|42.912}

R/Markdown (.Rmd) would, of course, be possible as well. And both are hopefully straightforward to turn into dynamic exercises where the numbers are sampled randomly. Let us know if you need further help with this.


问:
maatwebsite 3.1 stores formula text not calculated value
I am importing microsoft excel files to a database using maatwebsite 3.1. All cell with no formula were imported perfectly. but those cells with formula store the text of the formula not the calculated value.
I tried "use Maatwebsite\Excel\Concerns\WithCalculatedFormulas;". I also tried to store the value on disk before importing it.

I keep on getting something like =IF(ISNUMBER(GEOMEAN(G5:AD5)),G5/H5," - ") in the database where I should get 123.4567

答:
Ok.
1.I have to write my Imports/App/XyzImports using WithCalculatedFormulas
it worked perfectly. </p>


问:
I want to understand the concept of list comprehension in python

Given code

```<code>x=[(a,b) for a in range(3) for b in range(a)]
print(x)
</code>```

Output

```<code>[(1, 0), (2, 0), (2, 1)]
</code>```

I know the comprehension can be broken down as follows:

```<code>for a in range(3):
    for b in range(a):
        print(a,b)
</code>```

Can someone explain me how the output came as such provided above....

答:
You are running in parallel range(3) and range(a).

What effectively happens is that a is being set to 0, 1 and finally 2.

Then, your b is defined for each element in range(a).

Now data is generated:

```<code>a = 0
range(0) # nothing
a = 1
range(1) # (0, )
(1, 0) # first pair of (a, b) for a = 1
a = 2
range(2) # (0, 1)
(2, 0) # first pair of (a, b) for a = 2
(2, 1) # second pair of (a, b) for a = 2
# end
</code>```

Given the above, result ends up being [(1, 0), (2, 0), (2, 1)]

Body:
Given code

x=[(a,b) for a in range(3) for b in range(a)]
print(x)

Output

[(1, 0), (2, 0), (2, 1)]

I know the comprehension can be broken down as follows:

for a in range(3):
    for b in range(a):
        print(a,b)

Can someone explain me how the output came as such provided above....

Body:
You are running in parallel range(3) and range(a).

What effectively happens is that a is being set to 0, 1 and finally 2.

Then, your b is defined for each element in range(a).

Now data is generated:

a = 0
range(0) # nothing
a = 1
range(1) # (0, )
(1, 0) # first pair of (a, b) for a = 1
a = 2
range(2) # (0, 1)
(2, 0) # first pair of (a, b) for a = 2
(2, 1) # second pair of (a, b) for a = 2
# end

Given the above, result ends up being [(1, 0), (2, 0), (2, 1)]


问:
Laravel 6 send mail
I have an issue with authentication while sending mail with Laravel smtp driver.

.env file:

```<code>MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=account@gmail.com
MAIL_FROM_ADDRESS=account@gmail.com
MAIL_PASSWORD=app_password
MAIL_ENCRYPTION=tls
</code>```

It gives the following error:

"530-5.7.0 Authentication Required. "

2-Factor auth is enabled and app password is used;

Also tried with SSL on 465, same error;

Config is flushed every time .env is changed;

Everything in config/mail corresponds to .env;

SO all typical solutions are not working. What else may cause this problem?

I have also tried using smtp.vivaldi.net with vivaldi account, it authenticates, but gives an error:

"no valid recipients"

while recipient email is surely valid, which is also strange for me.

答:
Firstly, you need to setup 2 step verification here google security.
An App Password link will appear and you can get your App Password to insert into below "MAIL_PASSWORD". More info on getting App Password here

```<code>MAIL_DRIVER=smtp
MAIL_FROM_ADDRESS=noreply@talhaf.com
MAIL_FROM_NAME=System
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=YOUR_GMAIL@gmail.com
MAIL_PASSWORD=YOUR_GMAIL_CREATED_APP_PASSWORD
MAIL_ENCRYPTION=tls
</code>```

Don't forget to clear cache with :

```<code>php artisan config:cache
</code>```

Title: Laravel 6 send mail

Body:
I have an issue with authentication while sending mail with Laravel smtp driver.

.env file:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=account@gmail.com
MAIL_FROM_ADDRESS=account@gmail.com
MAIL_PASSWORD=app_password
MAIL_ENCRYPTION=tls

It gives the following error:

"530-5.7.0 Authentication Required. "

2-Factor auth is enabled and app password is used;

Also tried with SSL on 465, same error;

Config is flushed every time .env is changed;

Everything in config/mail corresponds to .env;

SO all typical solutions are not working. What else may cause this problem?

I have also tried using smtp.vivaldi.net with vivaldi account, it authenticates, but gives an error:

"no valid recipients"

while recipient email is surely valid, which is also strange for me.

Body:
Firstly, you need to setup 2 step verification here google security.
An App Password link will appear and you can get your App Password to insert into below "MAIL_PASSWORD". More info on getting App Password here

MAIL_DRIVER=smtp
MAIL_FROM_ADDRESS=noreply@talhaf.com
MAIL_FROM_NAME=System
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=YOUR_GMAIL@gmail.com
MAIL_PASSWORD=YOUR_GMAIL_CREATED_APP_PASSWORD
MAIL_ENCRYPTION=tls

Don't forget to clear cache with :

php artisan config:cache


问:
Python Social Auth, Google, and refresh token
In a personal project, I am trying to use Django as my front end and then allow data entered by users in a particular form to be copied to google sheets.

Google's own docs recommend using https://github.com/google/oauth2client which is now deprecated, and the docs have not been updated. With this, I have started attempting to use Python Social Auth and Gspread. For Gspread to be able to function correctly, I need to be able to pass it not only an access token but also a refresh token. Python Social Auth however is not persisting the refresh token along with the rest of the "extra data". Looking at the data preserved and the URLs routed to, it seems to me more like somewhere it is routing through Google+.

I have the following configurations in my Django settings files:

```<code>AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GoogleOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
    'social_core.pipeline.social_auth.associate_by_email',
)

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/spreadsheets']
</code>```

Is there a better way to access a google sheet?

Am I correct that PSA or google is redirecting me into a Google+ auth flow instead of the Google Oauth2?

If not, what must change so that Python Social Auth keeps the refresh token?

答:
You can send extra parameters to the OAuth2 provider using the variable

```<code>SOCIAL_AUTH_<PROVIDER>_AUTH_EXTRA_ARGUMENTS
</code>```

For Google, you can see the extra parameters they accept in their documentation (scroll down to "parameters"). The one we are looking for is access_type:

access_type: Indicates whether your application can refresh access tokens when the user is not present at the browser. Valid parameter values are online, which is the default value, and offline.

So we can add the following to settings.py, to indicate that we want to receive a refresh token:

```<code>SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_ARGUMENTS = {"access_type: offline"}
</code>```

The results from EXTRA_ARGUMENTS will be stored in extra_data, so the refresh token can be accessed like this:

refresh_token = user.social_auth.get(provider="google-oauth2").extra_data["refresh_token"]
```

<hr>

<p>One possible solution is to store the refresh token alongside the user in a  <code>UserProfile</code> model, by adding a custom function to the social-auth pipeline:</p>

<ol>
<li>Create the model</li>
</ol>

<pre class="lang-py prettyprint-override"><code># models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    refresh_token = models.CharField(max_length=255, default="")
</code>```

Add a function to access store the refresh token

# pipeline.py

from .models import UserProfile

def store_refresh_token(user=none, *args, **kwargs):
    extra_data = user.social_auth.get(provider="google-oauth2").extra_data
    UserProfile.objects.get_or_create(
        user=user, defaults={"refresh_token": extra_data["refresh_token"]}
    )
```

<ol start="3">
<li>Add our new function to the social-auth pipeline. </li>
</ol>

<pre class="lang-py prettyprint-override"><code># settings.py

...

SOCIAL_AUTH_PIPELINE = (
    ...
    "my-app.pipeline.store_refresh_token"
)

SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
    'https://www.googleapis.com/auth/spreadsheets'
    # any other scopes you need
]

...
</code>```

The token is now stored alongside the user and can be used to initialise the sheets client or whatever else you need.

ContentLicense: CC BY-SA 3.0

Body:
In a personal project, I am trying to use Django as my front end and then allow data entered by users in a particular form to be copied to google sheets.

Google's own docs recommend using https://github.com/google/oauth2client which is now deprecated, and the docs have not been updated. With this, I have started attempting to use Python Social Auth and Gspread. For Gspread to be able to function correctly, I need to be able to pass it not only an access token but also a refresh token. Python Social Auth however is not persisting the refresh token along with the rest of the "extra data". Looking at the data preserved and the URLs routed to, it seems to me more like somewhere it is routing through Google+.

I have the following configurations in my Django settings files:

AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GoogleOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
    'social_core.pipeline.social_auth.associate_by_email',
)

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '...'
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['https://www.googleapis.com/auth/spreadsheets']

Is there a better way to access a google sheet?

Am I correct that PSA or google is redirecting me into a Google+ auth flow instead of the Google Oauth2?

If not, what must change so that Python Social Auth keeps the refresh token?

Body:
You can send extra parameters to the OAuth2 provider using the variable

SOCIAL_AUTH_<PROVIDER>_AUTH_EXTRA_ARGUMENTS

For Google, you can see the extra parameters they accept in their documentation (scroll down to "parameters"). The one we are looking for is access_type:

access_type: Indicates whether your application can refresh access tokens when the user is not present at the browser. Valid parameter values are online, which is the default value, and offline.

So we can add the following to settings.py, to indicate that we want to receive a refresh token:

SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_ARGUMENTS = {"access_type: offline"}

The results from EXTRA_ARGUMENTS will be stored in extra_data, so the refresh token can be accessed like this:

refresh_token = user.social_auth.get(provider="google-oauth2").extra_data["refresh_token"]

One possible solution is to store the refresh token alongside the user in a  UserProfile model, by adding a custom function to the social-auth pipeline:

Create the model

# models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    refresh_token = models.CharField(max_length=255, default="")

Add a function to access store the refresh token

# pipeline.py

from .models import UserProfile

def store_refresh_token(user=none, *args, **kwargs):
    extra_data = user.social_auth.get(provider="google-oauth2").extra_data
    UserProfile.objects.get_or_create(
        user=user, defaults={"refresh_token": extra_data["refresh_token"]}
    )

Add our new function to the social-auth pipeline.

# settings.py

...

SOCIAL_AUTH_PIPELINE = (
    ...
    "my-app.pipeline.store_refresh_token"
)

SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
    'https://www.googleapis.com/auth/spreadsheets'
    # any other scopes you need
]

...

The token is now stored alongside the user and can be used to initialise the sheets client or whatever else you need.


问:
How to change the active configuration profile in gcloud?
I have created a new configuration profile using command:

```<code>gcloud init
</code>```

and now I don't know how to switch to old configuration profile without override it.

Using gcloud config I can't switch to another configuration only set a property of the current configuration.

any idea?

答:
I wrote a small Bash tool for anyone who needs to do this on a regular basis: https://github.com/uhinze/gconf

To list configs: gconf

To switch to a different config: gconf <CONFIG>

To then switch to the previous config (helpful when you're working with 2): gconf -

Body:
I have created a new configuration profile using command:

gcloud init

and now I don't know how to switch to old configuration profile without override it.

Using gcloud config I can't switch to another configuration only set a property of the current configuration.

any idea?


问:
WebdriverIO giving errors when trying to run tests
I'm having problems running my WebdriverIO tests and I think it's to do with how I'm handling running behind a corporate proxy. Things used to work fine but suddenly they have stopped and after a few hours trying various things I've run out of ideas.

My proxy details are saved as windows system variables in the name of http_proxy and https_proxy. I haven't changed these. The format I'm using for a variable is like this - http://myusername:mypassword@myproxyaddress:8080.

I also get errors when connecting to the internet through an external connection so I'm not 100% its the proxy causing this.

I've tried update all my packages so my package file now looks like this:

```<code>{
  "name": "name",
  "version": "1.0.0",
  "description": "UI Tests",
  "main": "index.js",
  "scripts": {
    "test": "node_modules/.bin/wdio ./wdio.conf.js"
  },
  "author": "Matt Thomas",
  "license": "ISC",
  "devDependencies": {
    "@wdio/dot-reporter": "^5.18.6",
    "@wdio/local-runner": "^5.18.6",
    "@wdio/mocha-framework": "^5.18.6",
    "@wdio/selenium-standalone-service": "^5.16.10",
    "@wdio/spec-reporter": "^5.18.6",
    "@wdio/sync": "^5.18.6",
    "chai": "^4.2.0",
    "chai-webdriverio": "^1.0.0",
    "chromedriver": "^79.0.3",
    "local-runner": "^0.1.1-alpha",
    "wdio-chromedriver-service": "^5.0.2",
    "webdriver": "^5.18.6"
  },
  "dependencies": {
    "@wdio/cli": "^5.18.6"
  }
}
</code>```

My wdio.conf.js file is here(comments stripped out from actual file):

```<code>exports.config = {

    specs: [
        './test/**/*.js'
    ],
    // Patterns to exclude.
    exclude: [
        // 'path/to/excluded/files'
    ],

    maxInstances: 10,

    capabilities: [{

        maxInstances: 5,

        browserName: 'chrome'

    }],

    logLevel: 'trace',

    bail: 0,

    baseUrl: 'https://apps-dev.bhp.com/moc/',

    waitforTimeout: 15000,

    connectionRetryTimeout: 90000,

    connectionRetryCount: 3,

    services: ['chromedriver','selenium-standalone'],

    framework: 'mocha',

    reporters: ['spec','dot'],

    mochaOpts: {
        ui: 'bdd',
        timeout: 120000
    },

    beforeTest: function (test, context) {
        const chai = require('chai')
        const chaiWebdriver = require('chai-webdriverio').default

        chai.use(chaiWebdriver(browser))

        global.assert = chai.assert
        global.should = chai.should
        global.expect = chai.expect

    },

}
</code>```

And my terminal when i run the test:

```<code>PS C:\Test Automation\Projects\WebdriveriO\GlobalMoC> npm test -- --spec ./test/login.test.js

> globalmoc@1.0.0 test C:\Test Automation\Projects\WebdriveriO\GlobalMoC
> wdio ./wdio.conf.js "--spec" "./test/login.test.js"

Execution of 1 spec files started at 2020-02-14T00:54:12.038Z

2020-02-14T00:54:12.042Z DEBUG @wdio/utils:initialiseServices: initialise wdio service "chromedriver"
2020-02-14T00:54:12.131Z DEBUG @wdio/utils:initialiseServices: initialise wdio service "selenium-standalone"
2020-02-14T00:54:12.204Z INFO @wdio/cli:launcher: Run onPrepare hook
Starting ChromeDriver 79.0.3945.36 (3582db32b33893869b8c1339e8f4d9ed1816f143-refs/branch-heads/3945@{#614}) on port 4444
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
2020-02-14T00:54:12.793Z ERROR @wdio/cli:utils: A service failed in the 'onPrepare' hook
Error: Could not download https://selenium-release.storage.googleapis.com/3.14/IEDriverServer_Win32_3.14.0.zip: Error: unable to get local issuer certificate
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\selenium-standalone\lib\install.js:387:12)
    at Object.onceWrapper (events.js:286:20)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.onRequestError (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:881:8)
    at ClientRequest.emit (events.js:198:13)
    at ClientRequest.EventEmitter.emit (domain.js:448:20)
    at TLSSocket.socketErrorListener (_http_client.js:392:9)
    at TLSSocket.emit (events.js:198:13)
    at TLSSocket.EventEmitter.emit (domain.js:448:20)

Continue...
2020-02-14T00:54:12.831Z INFO @wdio/local-runner: Start worker 0-0 with arg: ./wdio.conf.js,--spec,./test/login.test.js
[0-0] 2020-02-14T00:54:14.120Z INFO @wdio/local-runner: Run worker command: run
[0-0] RUNNING in chrome - C:\Test Automation\Projects\WebdriveriO\GlobalMoC\test\login.test.js
[0-0] 2020-02-14T00:54:14.473Z DEBUG @wdio/local-runner:utils: init remote session
[0-0] 2020-02-14T00:54:14.477Z INFO webdriverio: Initiate new session using the webdriver protocol
2020-02-14T00:54:14.481Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.482Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
[0-0] 2020-02-14T00:54:14.629Z DEBUG webdriver: request failed due to missing body
[0-0] 2020-02-14T00:54:14.630Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

[0-0] 2020-02-14T00:54:14.630Z INFO webdriver: Retrying 1/3
2020-02-14T00:54:14.630Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.630Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
2020-02-14T00:54:14.661Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:14.661Z INFO webdriver: Retrying 2/3
2020-02-14T00:54:14.661Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.661Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
[0-0] 2020-02-14T00:54:14.661Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

2020-02-14T00:54:14.691Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

2020-02-14T00:54:14.722Z ERROR webdriver: Request failed due to Error: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

    at getErrorFromResponseBody (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:117:12)
    at Request._callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\request.js:111:64)
    at Request.self.callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:185:22)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1161:10)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at IncomingMessage.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1083:12)
    at Object.onceWrapper (events.js:286:20)
2020-02-14T00:54:14.723Z ERROR webdriver: Error: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

    at getErrorFromResponseBody (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:117:12)
    at Request._callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\request.js:111:64)
    at Request.self.callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:185:22)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1161:10)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at IncomingMessage.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1083:12)
    at Object.onceWrapper (events.js:286:20)
2020-02-14T00:54:14.724Z ERROR @wdio/runner: Error: Failed to create session.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

    at startWebDriverSession (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:45:11)
    at process._tickCallback (internal/process/next_tick.js:68:7)
[0-0] Error: Failed to create session.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>
<style type="text/css">
<!--
body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}
fieldset{padding:0 15px 10px 15px;}
h1{font-size:2.4em;margin:0;color:#FFF;}
h2{font-size:1.7em;margin:0;color:#CC0000;}
h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;}
#header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF;
background-color:#555555;}
#content{margin:0 0 0 2%;position:relative;}
.content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;}
-->
</style>
</head>
<body>
<div id="header"><h1>Server Error</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>406 - Client browser does not accept the MIME type of the requested page.</h2>
  <h3>The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.</h3>
 </fieldset></div>
</div>
</body>
</html>

[0-0] 2020-02-14T00:54:14.691Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:14.691Z INFO webdriver: Retrying 3/3
2020-02-14T00:54:14.691Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.692Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
2020-02-14T00:54:14.722Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:15.284Z DEBUG @wdio/local-runner: Runner 0-0 finished with exit code 1
[0-0] FAILED in chrome - C:\Test Automation\Projects\WebdriveriO\GlobalMoC\test\login.test.js
2020-02-14T00:54:15.302Z INFO @wdio/cli:launcher: Run onComplete hook

Spec Files:      0 passed, 1 failed, 1 total (100% completed) in 00:00:03

2020-02-14T00:54:15.306Z INFO @wdio/local-runner: Shutting down spawned worker
2020-02-14T00:54:15.569Z INFO @wdio/local-runner: Waiting for 0 to shut down gracefully
2020-02-14T00:54:15.578Z INFO @wdio/local-runner: shutting down
npm ERR! Test failed.  See above for more details.
</code>```

答:
Since you are trying to use selenium standalone service and behind a proxy, you should include below settings in your config file. This is working for me. I have included some documents you can refer to.

```<code>skipSeleniumInstall: false,
  seleniumInstallArgs: {
    requestOpts: {
      strictSSL: false,
      timeout: 10000,
      proxy: proxyurl,
    },
  },
</code>```

https://webdriver.io/docs/selenium-standalone-service.html#skipseleniuminstall

WebdriverIO: How to fix error "unknown error: cannot get automation extension"?

Cheers!

Body:
I'm having problems running my WebdriverIO tests and I think it's to do with how I'm handling running behind a corporate proxy. Things used to work fine but suddenly they have stopped and after a few hours trying various things I've run out of ideas.

My proxy details are saved as windows system variables in the name of http_proxy and https_proxy. I haven't changed these. The format I'm using for a variable is like this - http://myusername:mypassword@myproxyaddress:8080.

I also get errors when connecting to the internet through an external connection so I'm not 100% its the proxy causing this.

I've tried update all my packages so my package file now looks like this:

{
  "name": "name",
  "version": "1.0.0",
  "description": "UI Tests",
  "main": "index.js",
  "scripts": {
    "test": "node_modules/.bin/wdio ./wdio.conf.js"
  },
  "author": "Matt Thomas",
  "license": "ISC",
  "devDependencies": {
    "@wdio/dot-reporter": "^5.18.6",
    "@wdio/local-runner": "^5.18.6",
    "@wdio/mocha-framework": "^5.18.6",
    "@wdio/selenium-standalone-service": "^5.16.10",
    "@wdio/spec-reporter": "^5.18.6",
    "@wdio/sync": "^5.18.6",
    "chai": "^4.2.0",
    "chai-webdriverio": "^1.0.0",
    "chromedriver": "^79.0.3",
    "local-runner": "^0.1.1-alpha",
    "wdio-chromedriver-service": "^5.0.2",
    "webdriver": "^5.18.6"
  },
  "dependencies": {
    "@wdio/cli": "^5.18.6"
  }
}

My wdio.conf.js file is here(comments stripped out from actual file):

exports.config = {

    specs: [
        './test/**/*.js'
    ],
    // Patterns to exclude.
    exclude: [
        // 'path/to/excluded/files'
    ],

    maxInstances: 10,

    capabilities: [{

        maxInstances: 5,

        browserName: 'chrome'

    }],

    logLevel: 'trace',

    bail: 0,

    baseUrl: 'https://apps-dev.bhp.com/moc/',

    waitforTimeout: 15000,

    connectionRetryTimeout: 90000,

    connectionRetryCount: 3,

    services: ['chromedriver','selenium-standalone'],

    framework: 'mocha',

    reporters: ['spec','dot'],

    mochaOpts: {
        ui: 'bdd',
        timeout: 120000
    },

    beforeTest: function (test, context) {
        const chai = require('chai')
        const chaiWebdriver = require('chai-webdriverio').default

        chai.use(chaiWebdriver(browser))

        global.assert = chai.assert
        global.should = chai.should
        global.expect = chai.expect

    },

}

And my terminal when i run the test:

PS C:\Test Automation\Projects\WebdriveriO\GlobalMoC> npm test -- --spec ./test/login.test.js

> globalmoc@1.0.0 test C:\Test Automation\Projects\WebdriveriO\GlobalMoC
> wdio ./wdio.conf.js "--spec" "./test/login.test.js"

Execution of 1 spec files started at 2020-02-14T00:54:12.038Z

2020-02-14T00:54:12.042Z DEBUG @wdio/utils:initialiseServices: initialise wdio service "chromedriver"
2020-02-14T00:54:12.131Z DEBUG @wdio/utils:initialiseServices: initialise wdio service "selenium-standalone"
2020-02-14T00:54:12.204Z INFO @wdio/cli:launcher: Run onPrepare hook
Starting ChromeDriver 79.0.3945.36 (3582db32b33893869b8c1339e8f4d9ed1816f143-refs/branch-heads/3945@{#614}) on port 4444
Only local connections are allowed.
Please protect ports used by ChromeDriver and related test frameworks to prevent access by malicious code.
2020-02-14T00:54:12.793Z ERROR @wdio/cli:utils: A service failed in the 'onPrepare' hook
Error: Could not download https://selenium-release.storage.googleapis.com/3.14/IEDriverServer_Win32_3.14.0.zip: Error: unable to get local issuer certificate
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\selenium-standalone\lib\install.js:387:12)
    at Object.onceWrapper (events.js:286:20)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.onRequestError (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:881:8)
    at ClientRequest.emit (events.js:198:13)
    at ClientRequest.EventEmitter.emit (domain.js:448:20)
    at TLSSocket.socketErrorListener (_http_client.js:392:9)
    at TLSSocket.emit (events.js:198:13)
    at TLSSocket.EventEmitter.emit (domain.js:448:20)

Continue...
2020-02-14T00:54:12.831Z INFO @wdio/local-runner: Start worker 0-0 with arg: ./wdio.conf.js,--spec,./test/login.test.js
[0-0] 2020-02-14T00:54:14.120Z INFO @wdio/local-runner: Run worker command: run
[0-0] RUNNING in chrome - C:\Test Automation\Projects\WebdriveriO\GlobalMoC\test\login.test.js
[0-0] 2020-02-14T00:54:14.473Z DEBUG @wdio/local-runner:utils: init remote session
[0-0] 2020-02-14T00:54:14.477Z INFO webdriverio: Initiate new session using the webdriver protocol
2020-02-14T00:54:14.481Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.482Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
[0-0] 2020-02-14T00:54:14.629Z DEBUG webdriver: request failed due to missing body
[0-0] 2020-02-14T00:54:14.630Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

[0-0] 2020-02-14T00:54:14.630Z INFO webdriver: Retrying 1/3
2020-02-14T00:54:14.630Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.630Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
2020-02-14T00:54:14.661Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:14.661Z INFO webdriver: Retrying 2/3
2020-02-14T00:54:14.661Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.661Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
[0-0] 2020-02-14T00:54:14.661Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

2020-02-14T00:54:14.691Z WARN webdriver: Request failed due to <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

2020-02-14T00:54:14.722Z ERROR webdriver: Request failed due to Error: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

    at getErrorFromResponseBody (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:117:12)
    at Request._callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\request.js:111:64)
    at Request.self.callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:185:22)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1161:10)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at IncomingMessage.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1083:12)
    at Object.onceWrapper (events.js:286:20)
2020-02-14T00:54:14.723Z ERROR webdriver: Error: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

    at getErrorFromResponseBody (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:117:12)
    at Request._callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\request.js:111:64)
    at Request.self.callback (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:185:22)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at Request.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1161:10)
    at Request.emit (events.js:198:13)
    at Request.EventEmitter.emit (domain.js:448:20)
    at IncomingMessage.<anonymous> (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\request\request.js:1083:12)
    at Object.onceWrapper (events.js:286:20)
2020-02-14T00:54:14.724Z ERROR @wdio/runner: Error: Failed to create session.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

    at startWebDriverSession (C:\Test Automation\Projects\WebdriveriO\GlobalMoC\node_modules\webdriver\build\utils.js:45:11)
    at process._tickCallback (internal/process/next_tick.js:68:7)
[0-0] Error: Failed to create session.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>406 - Client browser does not accept the MIME type of the requested page.</title>

Server Error

<fieldset>

406 - Client browser does not accept the MIME type of the requested page.

The page you are looking for cannot be opened by your browser because it has a file name extension that your browser does not accept.

 </fieldset>

[0-0] 2020-02-14T00:54:14.691Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:14.691Z INFO webdriver: Retrying 3/3
2020-02-14T00:54:14.691Z INFO webdriver: [POST] http://127.0.0.1:4444/session
2020-02-14T00:54:14.692Z INFO webdriver: DATA { capabilities:
   { alwaysMatch: { browserName: 'chrome' }, firstMatch: [ {} ] },
  desiredCapabilities: { browserName: 'chrome' } }
2020-02-14T00:54:14.722Z DEBUG webdriver: request failed due to missing body
2020-02-14T00:54:15.284Z DEBUG @wdio/local-runner: Runner 0-0 finished with exit code 1
[0-0] FAILED in chrome - C:\Test Automation\Projects\WebdriveriO\GlobalMoC\test\login.test.js
2020-02-14T00:54:15.302Z INFO @wdio/cli:launcher: Run onComplete hook

Spec Files:      0 passed, 1 failed, 1 total (100% completed) in 00:00:03

2020-02-14T00:54:15.306Z INFO @wdio/local-runner: Shutting down spawned worker
2020-02-14T00:54:15.569Z INFO @wdio/local-runner: Waiting for 0 to shut down gracefully
2020-02-14T00:54:15.578Z INFO @wdio/local-runner: shutting down
npm ERR! Test failed.  See above for more details.

Body:
Since you are trying to use selenium standalone service and behind a proxy, you should include below settings in your config file. This is working for me. I have included some documents you can refer to.

skipSeleniumInstall: false,
  seleniumInstallArgs: {
    requestOpts: {
      strictSSL: false,
      timeout: 10000,
      proxy: proxyurl,
    },
  },

https://webdriver.io/docs/selenium-standalone-service.html#skipseleniuminstall

WebdriverIO: How to fix error "unknown error: cannot get automation extension"?

Cheers!


问:
Dissecting Number Ranges
I'm trying to figure out the most efficient way to generate a WHERE query. I asked another question earlier, which was similar, but I will get right to the point on this one.

Given a collection of number ranges, ie 1-1000, 1500-1600 it is quite simple to create a mysql where condition to select records which are between these values.

ie, you would just do:

WHERE (lft BETWEEN 1 and 1000) OR (lft BETWEEN 1500-1600). However, what if you wanted to incorporate a NOT BETWEEN as well.

For example, if you define several rules, like...

ALLOW BETWEEN 1 - 1000

ALLOW BETWEEN 1500 - 1600

ALLOW BETWEEN 1250 - 1300

DENY BETWEEN 25 - 50

How can I merge these rules in order to efficiently generate a WHERE condition.
I would like the WHERE to dissect the ALLOW BETWEEN 1 - 1000 in order to create a gap in it. So that it would become 1-24 and 51-1000. Because the DENY rule is defined after the first rule, it "overwrites" the previous rules.

As another example,
Say that you have

ALLOW BETWEEN 5 - 15

DENY BETWEEN 10 - 50

ALLOW BETWEEN 45 - 60

Then I would like to generate a WHERE condition which would allow me to do:

WHERE (lft BETWEEN 5 and 9) OR (lft BETWEEN 45 and 60).

Notes (Edits)

Also, the maximum range that would ever allowed is 1 - 5600000. (Which would be 'Earth') ie. Allow everything on Earth.

The number ranges are actually the LEFT values in a NESTED SET MODEL. These aren't unique keys.
You can read why I want to do this in this question I asked earlier.
https://stackoverflow.com/questions/6020553/generating-a-mysql-between-where-condition-based-on-an-access-ruleset

Possible important note on my number ranges I maybe shouldn't have used the sample example which I did, but one important note about the nature of the number ranges is that, the ranges should actually always entirely consume or be consumed by a previous rule. For example, I used the example above, 10-50 allow, and deny 45-60. This wouldn't actually ever happen in my data set. It would actually be, allow 10-50, then the DENY would have to either be entirely consumed by that range, ie, 34-38. OR, entirely consume the previous rule. 9-51. This is because the ranges actually represent lft and rgt values in a nested set model and you cannot have overlaps like I presented.

I didn't think to mention that when asking the question, but after seeing the working sample code below, I can see that this note is actually important.

(Edited example mysql to include OR instead of AND as per comment below)

答:
Honestly, why bother?  As long as the key you're querying against is indexed, just put the multiple queries in there:

```<code>WHERE (foo BETWEEN 1 AND 1000
        OR foo BETWEEN 1500 AND 1600
        OR foo BETWEEN 1250 AND 1300
    ) AND (
        foo NOT BETWEEN 25 AND 50
    )
</code>```

You could squeze a slight bit of efficiency by building a dissector, but I would question if it's worth it.  All the WHERE clause items would be off of an index, so you're not preventing any hard operation from occurring (meaning you're not stopping a full-table-scan by doing it).

So rather than spending time building a system to do it for you, just implement an easy solution (ORing together the Allows, and ANDing together the Denys) and move on to more important things.  Then if it becomes a problem later, revisit it then.  But I really don't think this will ever become too big of a problem...

Edit Ok, here's a very simple algorithm for doing this.  It uses strings as the data store, so it's reasonably efficient for smaller numbers (below 1 million):

```<code>class Dissector {
    protected $range = '';
    public function allow($low, $high) {
        $this->replaceWith($low, $high, '1');
    }
    public function deny($low, $high) {
        $this->replaceWith($low, $high, '0');
    }
    public function findRanges() {
        $matches = array();
        preg_match_all(
            '/(?<!1)1+(?!1)/',
            $this->range,
            $matches,
            PREG_OFFSET_CAPTURE
        );
        return $this->decodeRanges($matches[0]);
    }
    public function generateSql($field) {
        $ranges = $this->findRanges();
        $where = array();
        foreach ($ranges as $range) {
            $where[] = sprintf(
                '%s BETWEEN %d AND %d',
                $field,
                $range['from'],
                $range['to']
            );
        }
        return implode(' OR ', $where);
    }
    protected function decodeRanges(array $matches) {
        $range = array();
        foreach ($matches as $match) {
            $range[] = array(
                'from' => $match[1] + 1,
                'to' => ($match[1] + strlen($match[0]))
            );
        }
        return $range;
    }
    protected function normalizeLengthTo($size) {
        if (strlen($this->range) < $size) {
            $this->range = str_pad($this->range, $size, '0');
        }
    }
    protected function replaceWith($low, $high, $character) {
        $this->normalizeLengthTo($high);
        $length = $high - $low + 1;
        $stub = str_repeat($character, $length);
        $this->range = substr_replace($this->range, $stub, $low - 1, $length);
    }
}
</code>```

Usage:

```<code>$d = new Dissector();
$d->allow(1, 10);
$d->deny(5, 15);
$d->allow(10, 20);
var_dump($d->findRanges());
var_dump($d->generateSql('foo'));
</code>```

Generates:

```<code>array(2) {
  [0]=>
  array(2) {
    ["from"]=>
    int(1)
    ["to"]=>
    int(4)
  }
  [1]=>
  array(2) {
    ["from"]=>
    int(10)
    ["to"]=>
    int(20)
  }
}
string(44) "foo BETWEEN 1 AND 4 OR foo BETWEEN 10 AND 20"
</code>```

Title: Dissecting Number Ranges

Body:
Honestly, why bother?  As long as the key you're querying against is indexed, just put the multiple queries in there:

WHERE (foo BETWEEN 1 AND 1000
        OR foo BETWEEN 1500 AND 1600
        OR foo BETWEEN 1250 AND 1300
    ) AND (
        foo NOT BETWEEN 25 AND 50
    )

You could squeze a slight bit of efficiency by building a dissector, but I would question if it's worth it.  All the WHERE clause items would be off of an index, so you're not preventing any hard operation from occurring (meaning you're not stopping a full-table-scan by doing it).

So rather than spending time building a system to do it for you, just implement an easy solution (ORing together the Allows, and ANDing together the Denys) and move on to more important things.  Then if it becomes a problem later, revisit it then.  But I really don't think this will ever become too big of a problem...

Edit Ok, here's a very simple algorithm for doing this.  It uses strings as the data store, so it's reasonably efficient for smaller numbers (below 1 million):

class Dissector {
    protected $range = '';
    public function allow($low, $high) {
        $this->replaceWith($low, $high, '1');
    }
    public function deny($low, $high) {
        $this->replaceWith($low, $high, '0');
    }
    public function findRanges() {
        $matches = array();
        preg_match_all(
            '/(?<!1)1+(?!1)/',
            $this->range,
            $matches,
            PREG_OFFSET_CAPTURE
        );
        return $this->decodeRanges($matches[0]);
    }
    public function generateSql($field) {
        $ranges = $this->findRanges();
        $where = array();
        foreach ($ranges as $range) {
            $where[] = sprintf(
                '%s BETWEEN %d AND %d',
                $field,
                $range['from'],
                $range['to']
            );
        }
        return implode(' OR ', $where);
    }
    protected function decodeRanges(array $matches) {
        $range = array();
        foreach ($matches as $match) {
            $range[] = array(
                'from' => $match[1] + 1,
                'to' => ($match[1] + strlen($match[0]))
            );
        }
        return $range;
    }
    protected function normalizeLengthTo($size) {
        if (strlen($this->range) < $size) {
            $this->range = str_pad($this->range, $size, '0');
        }
    }
    protected function replaceWith($low, $high, $character) {
        $this->normalizeLengthTo($high);
        $length = $high - $low + 1;
        $stub = str_repeat($character, $length);
        $this->range = substr_replace($this->range, $stub, $low - 1, $length);
    }
}

Usage:

$d = new Dissector();
$d->allow(1, 10);
$d->deny(5, 15);
$d->allow(10, 20);
var_dump($d->findRanges());
var_dump($d->generateSql('foo'));

Generates:

array(2) {
  [0]=>
  array(2) {
    ["from"]=>
    int(1)
    ["to"]=>
    int(4)
  }
  [1]=>
  array(2) {
    ["from"]=>
    int(10)
    ["to"]=>
    int(20)
  }
}
string(44) "foo BETWEEN 1 AND 4 OR foo BETWEEN 10 AND 20"


问:
PostgreSQL comparing null values in case statement
when I wrote a case statement to compare values in tables, it came unstuck with variables that are null. It thinks they are different (note: col1 is a character field).

```<code>select a.id,
       a.col1 as a_col1,
       b.col1 as b.col1,
       case when a.col1=b.col1 then 0 else 1 end as chk_col1
from   tablea a,
       tableb b
where a.id=b.id;
</code>```

... chk_col1 is always 0 when both col1's are null. I've tried

```<code>coalesce(a.col1,'null') as coalesce(b.col1,'null')
</code>```

but this didn't work either. It still returned 1 for chk_col1.

答:
SOLUTION! :
The variable referenced in the colaesce function must be the calculated one, i.e.

```<code>coalesce(a_col1,'null') as coalesce(b_col1,'null')
</code>```

Another thing I discovered. Let's say col2 is numeric. The above doesn't work, you'd need to use a 0. Or ... more cunningly, you can use '0', i.e.

```<code>coalesce(a_col2,'0') as coalesce(b_col2,'0')
</code>```

This is handy to know if you want to generate some code to compare tables by referencing pg_tables or svv_columns. In this code I had 2 tables that I'd created by reading svv_columns metadata table, and I wanted to created a case statement for each variable, so I'd have the two variables from each table side by side plus a check variable which I'd use for summarising later:

```<code>select '       coalesce(a.'||a.column_name||',''0'') as a_'||a.column_name||', coalesce(b.'||b.column_name||',''0'') as b_'||b.column_name||', case when a_'||a.column_name||'=b_'||b.column_name||' then 0 else 1 end as chk_'||a.column_name||','
from   tbl_a_vars a,
       tbl_b_vars b
where a.column_name=b.column_name;
</code>```

Body:
when I wrote a case statement to compare values in tables, it came unstuck with variables that are null. It thinks they are different (note: col1 is a character field).

select a.id,
       a.col1 as a_col1,
       b.col1 as b.col1,
       case when a.col1=b.col1 then 0 else 1 end as chk_col1
from   tablea a,
       tableb b
where a.id=b.id;

... chk_col1 is always 0 when both col1's are null. I've tried

coalesce(a.col1,'null') as coalesce(b.col1,'null')

but this didn't work either. It still returned 1 for chk_col1.

Body:
SOLUTION! :
The variable referenced in the colaesce function must be the calculated one, i.e.

coalesce(a_col1,'null') as coalesce(b_col1,'null')

Another thing I discovered. Let's say col2 is numeric. The above doesn't work, you'd need to use a 0. Or ... more cunningly, you can use '0', i.e.

coalesce(a_col2,'0') as coalesce(b_col2,'0')

This is handy to know if you want to generate some code to compare tables by referencing pg_tables or svv_columns. In this code I had 2 tables that I'd created by reading svv_columns metadata table, and I wanted to created a case statement for each variable, so I'd have the two variables from each table side by side plus a check variable which I'd use for summarising later:

select '       coalesce(a.'||a.column_name||',''0'') as a_'||a.column_name||', coalesce(b.'||b.column_name||',''0'') as b_'||b.column_name||', case when a_'||a.column_name||'=b_'||b.column_name||' then 0 else 1 end as chk_'||a.column_name||','
from   tbl_a_vars a,
       tbl_b_vars b
where a.column_name=b.column_name;


问:
How to break line in REXX
Can anyone tell me how to break line in rexx language.
E.g:
say "ala has a cat"0D0A"Cat has a ala"

in result I would like have:
ala has a cat
Cat has a ala

I want to post data to a pushgateway server and the syntax requires enter at the end of the line

答:
Hex 0D0A is a carriage return new line so if you add that to the end of your string it will look like an "enter". If you don't need the new line just remove the 0A from the CRLF variable.

```<code>CRLF = "0D0A"x
say "ala has a cat Cat has a ala" CRLF
</code>```

If you need each line separately use

```<code>CRLF = "0D0A"x
say "ala has a cat" CRLF "Cat has a ala" CRLF
</code>```

Title: How to break line in REXX

Body:
Hex 0D0A is a carriage return new line so if you add that to the end of your string it will look like an "enter". If you don't need the new line just remove the 0A from the CRLF variable.

CRLF = "0D0A"x
say "ala has a cat Cat has a ala" CRLF

If you need each line separately use

CRLF = "0D0A"x
say "ala has a cat" CRLF "Cat has a ala" CRLF


问:
Ordering dataframe columns row by row
I have a df that looks like this

```<code>df = pd.DataFrame({'A' : ['1','2','3','7'],
                   'B' : [7,6,5,4],
                   'C' : [5,8,7,1],
                   'v' : [1,9,9,8]})
df=df.set_index('A')
df

    B   C   v
A
1   7   5   1
2   6   8   9
3   5   7   9
7   4   1   8
</code>```

I'd like to sort it so that column B and C respectively show the lower and higher value of the two, i.e.

```<code>    B   C   v
A
1   5   7   1
2   6   8   9
3   5   7   9
7   1   4   8
</code>```

I can do this in a for loop that goes through each row, but I'm wondering if there is a better way to do it?

答:
If performance is important use numpy.sort with axis=1:

```<code>df[['B','C']] = np.sort(df[['B','C']], axis=1)
print (df)
   B  C  v
A
1  5  7  1
2  6  8  9
3  5  7  9
7  1  4  8
</code>```

Title: Ordering dataframe columns row by row

Body:
I have a df that looks like this

df = pd.DataFrame({'A' : ['1','2','3','7'],
                   'B' : [7,6,5,4],
                   'C' : [5,8,7,1],
                   'v' : [1,9,9,8]})
df=df.set_index('A')
df

    B   C   v
A
1   7   5   1
2   6   8   9
3   5   7   9
7   4   1   8

I'd like to sort it so that column B and C respectively show the lower and higher value of the two, i.e.

    B   C   v
A
1   5   7   1
2   6   8   9
3   5   7   9
7   1   4   8

I can do this in a for loop that goes through each row, but I'm wondering if there is a better way to do it?

Body:
If performance is important use numpy.sort with axis=1:

df[['B','C']] = np.sort(df[['B','C']], axis=1)
print (df)
   B  C  v
A
1  5  7  1
2  6  8  9
3  5  7  9
7  1  4  8


问:
Can't find variable: counter, in expo
Been using React for a bit and wanted to try out native using expo, when I click on a button to increment a counter, I get a ReferenceError on my phone saying "Can't find variable: counter", I don't get any error on the expo gui or in VSCode, very confused.</p>

```<code>import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { Button } from "react-native-elements";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

  increment() {
    this.setState({ counter: (counter += 1) });
  }

  render() {
    return (
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text>
        <Text>{this.state.counter}</Text>
        <Button title="Press Me" onPress={this.increment}></Button>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

</code>```

答:
Change your increment function to

```<code>increment = () => {
    this.setState({
        counter: this.state.counter + 1
    });
}
</code>```

Make sure to define your increment function as an arrow function otherwise you can't access it from Button.

```<code><Button title="Press Me" onPress={this.increment}></Button>
</code>```

Feel free for doubts.

Title: Can't find variable: counter, in expo

Body:
Been using React for a bit and wanted to try out native using expo, when I click on a button to increment a counter, I get a ReferenceError on my phone saying "Can't find variable: counter", I don't get any error on the expo gui or in VSCode, very confused.

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { Button } from "react-native-elements";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    };
  }

  increment() {
    this.setState({ counter: (counter += 1) });
  }

  render() {
    return (
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text>
        <Text>{this.state.counter}</Text>
        <Button title="Press Me" onPress={this.increment}></Button>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

Body:
Change your increment function to

increment = () => {
    this.setState({
        counter: this.state.counter + 1
    });
}

Make sure to define your increment function as an arrow function otherwise you can't access it from Button.

<Button title="Press Me" onPress={this.increment}></Button>

Feel free for doubts.


问:
Hibernate: saving an entity with foreign primary key
I have a database with two entities (Product and Feedback). Feedback has a single foreign key that refers to Product, whose primary key is an integer. They are therefore in a one-to-one relationship.

I want to add a new Feedback entry to the database with my web application.
To do so I'm trying to perform a PUT request to my Spring server from my Angular client application.

The client has an Object of class Feedback with the following attributes (content of Feedback.ts)

```<code>export class Feedback {
  constructor(
    public productId: number,
    public score: number,
    public preferred: boolean,
    public textNote: string
  ) {
  }
}
</code>```

The class Feedback.java, server side:

```<code>@Entity
@Table(name = "feedback")
public class Feedback implements Serializable {
    @Id
    @JoinColumn(name = "product", unique = true)
    @OneToOne(cascade = CascadeType.ALL)
    private Product product;

    @Column(name = "score")
    private double score;

    @Column(name = "preferred")
    private boolean preferred;

    @Column(name = "textnote")
    private String textNote;

    // ... getters, setters, constructor

}
</code>```

As you can see there is a non-matching attribute: productId is a number, while product is of class Product. How can I give the Spring application the correct object in order to save it in the database?

I'm trying to follow the Hibernate guidelines and as far as I understood it would be a bad practice to use an attribute in the java class of type int instead of Product. What should I do?

答:
You create a new Product with the productId from the FE. Now you can create a new Feedback object with all types set. This you can then store in the database.

[Edit]

Usually, you have data transfer object (DTO)in the backend. Those will as the name suggests transport data do nothing else. Endpoints in the backend will always get DTOs which are the same as in the FE. In your case create a FeedbackDto.

```<code>public class FeedbackDto {
   Long productId;
   Double score;
   Boolean preferred;
   String textNote;
}
</code>```

The Endpoint will receive this Object which has the same fields as the Feedback from the FE. Spring will create and fill you the object from the values in the JSON body of the request

From the FeedbackDto you create now a Feedback entity:

```<code>new Feedback(new Product(feedbackDto.productId), feedbackDto.score, feedbackDto.preferred, feedbackDto.textNote)
</code>```

Now you can save this Feedback entity into your database.

Body:
I have a database with two entities (Product and Feedback). Feedback has a single foreign key that refers to Product, whose primary key is an integer. They are therefore in a one-to-one relationship.

I want to add a new Feedback entry to the database with my web application.
To do so I'm trying to perform a PUT request to my Spring server from my Angular client application.

The client has an Object of class Feedback with the following attributes (content of Feedback.ts)

export class Feedback {
  constructor(
    public productId: number,
    public score: number,
    public preferred: boolean,
    public textNote: string
  ) {
  }
}

The class Feedback.java, server side:

@Entity
@Table(name = "feedback")
public class Feedback implements Serializable {
    @Id
    @JoinColumn(name = "product", unique = true)
    @OneToOne(cascade = CascadeType.ALL)
    private Product product;

    @Column(name = "score")
    private double score;

    @Column(name = "preferred")
    private boolean preferred;

    @Column(name = "textnote")
    private String textNote;

    // ... getters, setters, constructor

}

As you can see there is a non-matching attribute: productId is a number, while product is of class Product. How can I give the Spring application the correct object in order to save it in the database?

I'm trying to follow the Hibernate guidelines and as far as I understood it would be a bad practice to use an attribute in the java class of type int instead of Product. What should I do?

Body:
You create a new Product with the productId from the FE. Now you can create a new Feedback object with all types set. This you can then store in the database.

[Edit]

Usually, you have data transfer object (DTO)in the backend. Those will as the name suggests transport data do nothing else. Endpoints in the backend will always get DTOs which are the same as in the FE. In your case create a FeedbackDto.

public class FeedbackDto {
   Long productId;
   Double score;
   Boolean preferred;
   String textNote;
}

The Endpoint will receive this Object which has the same fields as the Feedback from the FE. Spring will create and fill you the object from the values in the JSON body of the request

From the FeedbackDto you create now a Feedback entity:

new Feedback(new Product(feedbackDto.productId), feedbackDto.score, feedbackDto.preferred, feedbackDto.textNote)

Now you can save this Feedback entity into your database.


问:
Bitcoin: How to generate BASE58 (P2SH) Addresses using python or other methods
Hello and thank you for your help.

I need to generate a big amount of bitcoin BASE58(P2SH) Addresses (starts with '3') and their private keys using python, or also with other methods you know, i just need to generate them.

I've tried a lot of methods also found here but didn't work.
(problems like i've genereted the addresses but the private key doesn't work).

I'll apprecciate your help.

Best Regards.

答:
You can use the BTCPY library to do it.
https://github.com/chainside/btcpy

There are many examples about how to do it in the documentation. Basically, you can use a private key to generate a public key, and then, generate the addresses from it.


问:
Laravel goutte form
I have some problems with Goutte submit the form. I'm trying to login to app, but every time when I refresh it's returning 404 error, but 404 in the app because as a final result I'm returning html() to see what is happening. Maybe 404 because my link is ex.

```<code>http://localhost:8000/trylogin
</code>```

Here is the code.

```<code>$client = new Client();
$crawler = $client->request('GET', 'https://app.productlistgenie.io/signin');
$form = $crawler->selectButton('LogIn')->form();
$crawler = $client->submit($form, array('email' => 'myemail@here.com', 'password' => 'mypasshere'));
$crawler->filter('.flash-error')->each(function ($node) {
   print $node->text()."\n";
});

return $crawler->html();
</code>```

答:
This is the working code </p>

```<code>$client = new Client();
$crawler = $client->request('GET', 'https://app.productlistgenie.io/signin');
$form = $crawler->selectButton('SIGN IN')->form();
$crawler = $client->submit($form, array('email' => 'myemail@here.com', 'password' => 'mypasshere'));
$crawler->filter('.font-roboto-light.color-dark-white')->each(function ($node) {
       print $node->text()."\n";
 });
return $crawler->html();
</code>```

Title: Laravel goutte form

Body:
I have some problems with Goutte submit the form. I'm trying to login to app, but every time when I refresh it's returning 404 error, but 404 in the app because as a final result I'm returning html() to see what is happening. Maybe 404 because my link is ex.

http://localhost:8000/trylogin

Here is the code.

$client = new Client();
$crawler = $client->request('GET', 'https://app.productlistgenie.io/signin');
$form = $crawler->selectButton('LogIn')->form();
$crawler = $client->submit($form, array('email' => 'myemail@here.com', 'password' => 'mypasshere'));
$crawler->filter('.flash-error')->each(function ($node) {
   print $node->text()."\n";
});

return $crawler->html();

Body:
This is the working code

$client = new Client();
$crawler = $client->request('GET', 'https://app.productlistgenie.io/signin');
$form = $crawler->selectButton('SIGN IN')->form();
$crawler = $client->submit($form, array('email' => 'myemail@here.com', 'password' => 'mypasshere'));
$crawler->filter('.font-roboto-light.color-dark-white')->each(function ($node) {
       print $node->text()."\n";
 });
return $crawler->html();


问:
Gradle sync fails with cmake "cause: executing external native build for cmake"
I'm trying to build my colleagues' project in Android Studio, which requires CMake SDK to build the external c/cpp files included in the project. The problem I'm running into is despite having installed the LLDB, NDK, and CMake SDK tools through SDK manager, the gradle for the module that references cmake path fails to sync. My colleagues who already have this project installed and working haven't run into this issue, so I suspect that it has to be something in my environment setting.

So far, I've tried uninstalling and reinstalling CMake SDK, refresh linked C++ projects, and removing the reference to the cmake path from the gradle file and adding the reference by right click the moduel -> Link C++ Project with Gradle, but none of these worked.

When I comment out the reference to my CMakeLists.txt in the gradle, it syncs, indicating that the problem is in regards to the reference to the CMake file. I also tried commenting out library references in my CMakeLists.txt file to see if the error is occurring due to a reference in the file, but even when I comment everything out, the gradle fails to sync.

This is what my gradle file looks like.

externalNativeBuild {
    cmake {
        path 'src/main/cpp/CMakeLists.txt'
    }
}
```

<p>When I press sync (try again) in build.gradle, the error message simply shows</p>

<pre class="lang-none prettyprint-override"><code>SIMPLE: Error configuring
</code>```

When I ignore the fact that gradle sync failed and just try to build, the error message shows as this.

Cause: executing external native build for cmake <my_project_path>\src\main\cpp\CMakeLists.txt
```

Edit: Added a link to the image capture of my Android Studio NDK path (C:\Users\username\AppData\Local\Android\Sdk\ndk-bundle for Windows).
NDK path Capture

答:
This could be because you have a shared library in the project which needs to be linked with the shared version of the STL. Try adding the following to your build.gradle:

```<code>android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_shared"
            }
        }
    }
}
</code>```

Body:
I'm trying to build my colleagues' project in Android Studio, which requires CMake SDK to build the external c/cpp files included in the project. The problem I'm running into is despite having installed the LLDB, NDK, and CMake SDK tools through SDK manager, the gradle for the module that references cmake path fails to sync. My colleagues who already have this project installed and working haven't run into this issue, so I suspect that it has to be something in my environment setting.

So far, I've tried uninstalling and reinstalling CMake SDK, refresh linked C++ projects, and removing the reference to the cmake path from the gradle file and adding the reference by right click the moduel -> Link C++ Project with Gradle, but none of these worked.

When I comment out the reference to my CMakeLists.txt in the gradle, it syncs, indicating that the problem is in regards to the reference to the CMake file. I also tried commenting out library references in my CMakeLists.txt file to see if the error is occurring due to a reference in the file, but even when I comment everything out, the gradle fails to sync.

This is what my gradle file looks like.

externalNativeBuild {
    cmake {
        path 'src/main/cpp/CMakeLists.txt'
    }
}

When I press sync (try again) in build.gradle, the error message simply shows

SIMPLE: Error configuring

When I ignore the fact that gradle sync failed and just try to build, the error message shows as this.

Cause: executing external native build for cmake <my_project_path>\src\main\cpp\CMakeLists.txt

Edit: Added a link to the image capture of my Android Studio NDK path (C:\Users\username\AppData\Local\Android\Sdk\ndk-bundle for Windows).
NDK path Capture

Body:
This could be because you have a shared library in the project which needs to be linked with the shared version of the STL. Try adding the following to your build.gradle:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_STL=c++_shared"
            }
        }
    }
}


问:
how to get the tweets from the stopped stream using twitter4s
i am using twitter4s https://github.com/DanielaSfregola/twitter4s here it shows how to we get the twitter stream and stops it </p>

```<code>def simulateNextActionAfterMillis(millis: Long): Future[Unit] = Future {
      Thread.sleep(millis); println()
    }

    for {
      streamA <- streamingClient.sampleStatuses(stall_warnings = true)(printTweetText)
      _ <- simulateNextActionAfterMillis(10000)
    } yield streamA.close()

def printTweetText: PartialFunction[StreamingMessage, Unit] = {
      case tweet: Tweet =>
        println(tweet.text)
    }
</code>```

<p>all i want is the total tweets object after the stream closes how can i get that ? </p>

答:
I could not really understand your issue by looking at the code, so can not answer the real question.

But I thought to point out that you should never simulate a future by "sleeping" on a thread.

You can create a non-blocking future using following,

```<code>import java.util.{Timer, TimerTask}

import scala.concurrent.{Future, Promise}
import scala.concurrent.duration.Duration
import scala.util.Try

object FutureUtils {

  val timer = new Timer();

  def createDelayedFuture[T](delayInMills: Long, creator: () => T): Future[T] = {
    val promise = Promise[T]()
    timer.schedule(
      new TimerTask {
        override def run(): Unit = promise.complete(Try(creator()))
      },
      delayInMills
    )
    promise.future
  }
}
</code>```

Body:
i am using twitter4s https://github.com/DanielaSfregola/twitter4s here it shows how to we get the twitter stream and stops it

def simulateNextActionAfterMillis(millis: Long): Future[Unit] = Future {
      Thread.sleep(millis); println()
    }

    for {
      streamA <- streamingClient.sampleStatuses(stall_warnings = true)(printTweetText)
      _ <- simulateNextActionAfterMillis(10000)
    } yield streamA.close()

def printTweetText: PartialFunction[StreamingMessage, Unit] = {
      case tweet: Tweet =>
        println(tweet.text)
    }

all i want is the total tweets object after the stream closes how can i get that ?

Body:
I could not really understand your issue by looking at the code, so can not answer the real question.

But I thought to point out that you should never simulate a future by "sleeping" on a thread.

You can create a non-blocking future using following,

import java.util.{Timer, TimerTask}

import scala.concurrent.{Future, Promise}
import scala.concurrent.duration.Duration
import scala.util.Try

object FutureUtils {

  val timer = new Timer();

  def createDelayedFuture[T](delayInMills: Long, creator: () => T): Future[T] = {
    val promise = Promise[T]()
    timer.schedule(
      new TimerTask {
        override def run(): Unit = promise.complete(Try(creator()))
      },
      delayInMills
    )
    promise.future
  }
}


问:
In my htaccess RewriteRule is not working
I just need to navigate to the subfolder.

```<code><IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^admin.mydomain.com [NC,OR]
    RewriteCond %{HTTP_HOST} ^www.admin.mydomain.com [NC]
    RewriteRule ^$ admin/App/ [L]
    RewriteRule (.*) admin/App/$1 [L,NC]
</IfModule>
</code>```

Can anyone help to show what's wrong in my .htaccess file.

答:
You may replace your code with this code:

```<code>RewriteEngine on

RewriteCond %{HTTP_HOST} ^(?:www\.)?admin\.mydomain\.com$ [NC]
RewriteRule ^$ admin/App/ [L]

RewriteCond %{HTTP_HOST} ^(?:www\.)?admin\.mydomain\.com$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .+ admin/App/$0 [L]
</code>```

Make sure .htacccess is enabled and this is placed in site root .htaccess.

Body:
I just need to navigate to the subfolder.

<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{HTTP_HOST} ^admin.mydomain.com [NC,OR]
    RewriteCond %{HTTP_HOST} ^www.admin.mydomain.com [NC]
    RewriteRule ^$ admin/App/ [L]
    RewriteRule (.*) admin/App/$1 [L,NC]
</IfModule>

Can anyone help to show what's wrong in my .htaccess file.

Body:
You may replace your code with this code:

RewriteEngine on

RewriteCond %{HTTP_HOST} ^(?:www\.)?admin\.mydomain\.com$ [NC]
RewriteRule ^$ admin/App/ [L]

RewriteCond %{HTTP_HOST} ^(?:www\.)?admin\.mydomain\.com$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .+ admin/App/$0 [L]

Make sure .htacccess is enabled and this is placed in site root .htaccess.


答:
Postgres supports the null-safe comparison operator is not distinct from.  So, try this:

```<code>select a.id,
       a.col1 as a_col1,
       b.col1 as b.col1,
       (case when a.col1 is not distinct from b.col1 then 0 else 1 end) as chk_col1
from tablea a join
     tableb b
     on a.id = b.id;
</code>```

Personally, I would leave the value as a boolean:

```<code>select a.id, a.col1 as a_col1, b.col1 as b.col1,
       (a.col1 is distinct from b.col1) as chk_col1
from tablea a join
     tableb b
     on a.id = b.id;
</code>```

Also note that I used proper, explicit, standard, readable JOIN syntax.

Title: PostgreSQL comparing null values in case statement

Body:
when I wrote a case statement to compare values in tables, it came unstuck with variables that are null. It thinks they are different (note: col1 is a character field).

select a.id,
       a.col1 as a_col1,
       b.col1 as b.col1,
       case when a.col1=b.col1 then 0 else 1 end as chk_col1
from   tablea a,
       tableb b
where a.id=b.id;

... chk_col1 is always 0 when both col1's are null. I've tried

coalesce(a.col1,'null') as coalesce(b.col1,'null')

but this didn't work either. It still returned 1 for chk_col1.

Body:
Postgres supports the null-safe comparison operator is not distinct from.  So, try this:

select a.id,
       a.col1 as a_col1,
       b.col1 as b.col1,
       (case when a.col1 is not distinct from b.col1 then 0 else 1 end) as chk_col1
from tablea a join
     tableb b
     on a.id = b.id;

Personally, I would leave the value as a boolean:

select a.id, a.col1 as a_col1, b.col1 as b.col1,
       (a.col1 is distinct from b.col1) as chk_col1
from tablea a join
     tableb b
     on a.id = b.id;

Also note that I used proper, explicit, standard, readable JOIN syntax.